package parser

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"gitee.com/u-language/u-language/ucom/ast"
	"gitee.com/u-language/u-language/ucom/check"
	"gitee.com/u-language/u-language/ucom/config"
	"gitee.com/u-language/u-language/ucom/data"
	"gitee.com/u-language/u-language/ucom/errcode"
	"gitee.com/u-language/u-language/ucom/internal/time"
	"gitee.com/u-language/u-language/ucom/internal/utils"
	"gitee.com/u-language/u-language/ucom/lex"
)

var NoUFileErr = errors.New("没有U文件")
var ImportLimit = errors.New("目前import只支持相对路径导入")
var PathNotFound = errors.New("找不到的路径")

var notest bool

// ParserPackage 转换一个目录下所有扩展名为.u的源代码文件为抽象语法树
func ParserPackage(dir string, thread bool, errctx *errcode.ErrCtx, sbt *ast.Sbt, ImportPackage *map[string]*ast.Package) (*ast.Package, error) {
	paths, err := utils.FindU(dir)
	if err != nil {
		return nil, err
	}
	if len(paths) == 0 {
		return nil, fmt.Errorf("%s目录%w", dir, NoUFileErr)
	}
	p := ast.NewPackage(dir, thread, errctx, sbt, ImportPackage)
	var wg = utils.NewWaitGroup(thread)
	var t time.Time
	var result = make(chan error, len(paths))
	for _, v := range paths { //将所有源文件进行词法分析后加入包中
		v := v
		wg.Go(func() {
			defer wg.Done()
			file, size, buf, err := openfileAndInfo(v)
			if err != nil {
				result <- fmt.Errorf("path=%s \t err=%w", v, err)
				return
			}
			var r *bufio.Reader
			if !test || notest { //如果非测试
				r = bufio.NewReaderSize(file, int(size))
			} else {
				r = bufio.NewReaderSize(bytes.NewBuffer(buf), int(size))
			}
			Filestr, err := utils.ReadAllLine(r)
			if err != nil {
				result <- fmt.Errorf("path=%s \t err=%w", v, err)
				return
			}
			if !test { //如果非测试
				file.Close()
			}
			FT := lex.Lex(v, Filestr, errctx, false) //进行词法分析
			if FT == nil {
				fmt.Println(v, "是空文件")
				return
			}
			if errctx.Errbol() { //如果词法分析有错误·
				return
			}
			p.AddFile(FT)
		})
	}
	wg.Wait()
	select {
	case err = <-result:
		if err != nil {
			return nil, err
		}
	default:
	}
	p.Parser()
	try_print(p)
	err = loadAllImport(dir, p.ImportTable, p.ImportPackage, &p.ImportLoacl, thread, errctx, p.Sbt)
	if err != nil {
		return nil, err
	}
	//进行语义检查
	try_new_print_check(&t)
	check.CheckPackage(p, errctx, thread)
	try_Print_Time(&t)
	return p, nil
}

// try_new_print_LexTime 尝试设置词法分析用时统计标题
func try_new_print_LexTime(t *time.Time) {
	if config.PrintTime == "on" { //如果启用打印用时
		*t = time.NewTime("词法分析用时：")
	}
}

// try_Print_Time 尝试打印用时
func try_Print_Time(t *time.Time) {
	if config.PrintTime == "on" { //如果启用打印用时
		t.PrintlnSubNow()
	}
}

// try_print_FT 尝试打印词法分析结果
func try_print_FT(FT *lex.FileToken) {
	if Debug && config.Debug_PrintLex == "on" { //调试操作
		fmt.Println(FT)
	}
}

// try_new_print_AstTime 尝试设置构建抽象语法树用时统计标题
func try_new_print_AstTime(t *time.Time) {
	if config.PrintTime == "on" { //如果启用打印用时
		*t = time.NewTime("构建抽象语法树用时：")
	}
}

// try_print 尝试打印
func try_print(result fmt.Stringer) {
	if Debug && config.Debug_PrintAst == "on" { //调试操作
		fmt.Println(result.String())
	}
}

// try_new_print_check 尝试设置语义检查用时统计标题
func try_new_print_check(t *time.Time) {
	if config.PrintTime == "on" { //如果启用打印用时
		*t = time.NewTime("语义检查用时：")
	}
}

// loadAllImport 获取导入包的Ast
//   - ImportTable 是导入表
//   - ImportPackage 是自己及依赖导入的包
//   - thread 控制是否并发
//   - errctx 是错误处理上下文
//   - importLoacl 自己导入的包
func loadAllImport(dir string, ImportTable *ast.Sbt, ImportPackage *map[string]*ast.Package, importLoacl *[]data.IsItARecursiveType, thread bool, errctx *errcode.ErrCtx, sbt *ast.Sbt) error {
	var importPath []string
	ImportTable.Range(func(key string, value ast.SymbolInfo) bool { //获取所有导入路径
		importPath = append(importPath, value.Info.(ast.ImportInfo).Path)
		return true
	})
	if *ImportPackage == nil {
		*ImportPackage = make(map[string]*ast.Package, len(importPath))
	}
	var err error
	for _, v := range importPath { //获取每个被导入的包
		v, err = importPathToAbs(dir, v)
		if err != nil {
			return err
		}
		p, ok := (*ImportPackage)[v]
		if ok { //如果已经被导入
			*importLoacl = append(*importLoacl, p)
			continue
		}
		p, err := loadImport(dir, v, thread, errctx, sbt, ImportPackage)
		if err != nil {
			return err
		}
		(*ImportPackage)[v] = p
		*importLoacl = append(*importLoacl, p)
		err = loadAllImport(p.Dir, p.ImportTable, ImportPackage, &p.ImportLoacl, thread, errctx, sbt) //导入被导入包导入的包
		if err != nil {
			return err
		}
	}
	return nil
}

func importPathToAbs(dir, path string) (string, error) {
	if !(strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")) {
		return "", fmt.Errorf("%s 不支持导入 因为%w", path, ImportLimit)
	}
	return filepath.Join(dir, path), nil
}

func loadImport(dir string, path string, thread bool, errctx *errcode.ErrCtx, sbt *ast.Sbt, ImportPackage *map[string]*ast.Package) (p *ast.Package, err error) {
	p, err = ParserPackage(path, thread, errctx, sbt, ImportPackage)
	if errors.Is(err, os.ErrNotExist) {
		return nil, fmt.Errorf("%w %s", PathNotFound, path)
	}
	return
}
